package com.laamella.syntherface.synths.midi;

/**
 * Lots of bit/byte twiddling utilities.
 * 
 * @author danny
 */
public final class Bits {
	private Bits() {
	}

	/**
	 * @param value
	 * @return the lower 7 bits of the value.
	 */
	public static int lsb(int value) {
		return value & 0x7f;
	}

	/**
	 * @param value
	 * @return the second-lowest 7 bits of the value
	 */
	public static int msb(int value) {
		return (value >> 7) & 0x7f;
	}

	public static long getUnsignedInt32At(byte[] data, int offset) {
		long b0 = ((long) data[offset]) & 0xFF;
		long b1 = ((long) data[offset + 1]) & 0xFF;
		long b2 = ((long) data[offset + 2]) & 0xFF;
		long b3 = ((long) data[offset + 3]) & 0xFF;
		return b0 << 24 | b1 << 16 | b2 << 8 | b3;
	}

	public static void setUnsignedInt32At(byte[] data, int offset, long value) {
		// TODO untested
		data[offset] = (byte) (value >> 24 & 0xFF);
		data[offset + 1] = (byte) (value >> 16 & 0xFF);
		data[offset + 2] = (byte) (value >> 8 & 0xFF);
		data[offset + 3] = (byte) (value & 0xFF);
	}

	public static final int[] LowBitMasks = new int[] { 0x00, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F, 0xFF };
	public static final int[] HighBitMasks = new int[] { 0xFF, 0xFE, 0xFC, 0xF8, 0xF0, 0xE0, 0xC0, 0x80, 0x00 };

	public static int getBitField(byte decoded, int shiftBitsRight, int amountOfBits) {
		return (decoded >> shiftBitsRight) & LowBitMasks[amountOfBits];
	}

	public static String byteToHex(int b) {
		String hex = "0" + Integer.toHexString((byte) b);
		if (hex.length() > 2) {
			hex = hex.substring(hex.length() - 2);
		}
		return hex;
	}

	public static String intToHex(long b) {
		String hex = "00000000" + Integer.toHexString((int) b);
		if (hex.length() > 8) {
			hex = hex.substring(hex.length() - 8);
		}
		return hex;
	}

	public static byte setBitField(int byteWithBitfieldToUpdate, int startBit, int bitCount, int value) {
		// clear field
		int mask = LowBitMasks[startBit] | HighBitMasks[startBit + bitCount];
		byteWithBitfieldToUpdate = byteWithBitfieldToUpdate & mask;
		// set value in cleared field.
		value = (value & LowBitMasks[bitCount]) << startBit;
		return (byte) (byteWithBitfieldToUpdate | value);
	}

	public static String padWithZeroes(String s, int size) {
		final String zeroes = "00000000000000000000000000000000000000";
		return zeroes.substring(0, size - s.length()) + s;
	}

	public static void debugDump(byte[] data, int width, boolean dumpBinary, boolean dumpHex, boolean dumpAscii) {
		for (int line = 0; line < data.length / width + 1; line++) {
			String binary = "";
			String hex = "";
			String ascii = "";
			for (int i = 0; i < width; i++) {
				int offset = line * width + i;
				if (offset < data.length) {
					int value = data[offset] & 0xFF;
					binary += padWithZeroes(Integer.toBinaryString(value), 8) + " ";
					hex += padWithZeroes(Integer.toHexString(value), 2) + " ";
					if (value >= 0x20) {
						ascii += (char) value;
					} else {
						ascii += "_";
					}
				} else {
					binary += "         ";
					hex += "   ";
					ascii += " ";
				}
			}
			System.out.println(hex + ascii + " " + binary);
		}
	}

	public static void debugDump(byte[] data) {
		debugDump(data, 16, true, true, true);
	}

	/**
	 * Takes a 7 bit msb and lsb, and makes a 14 bit word.
	 */
	public static int msblsb(int msb, int lsb) {
		return ((msb & 0x7F) << 7) | (lsb & 0x7F);
	}
}
